/*
 * Copyright 2007 Matt Jensen
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.jtell.internal;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jtell.*;
import org.jtell.config.Configuration;
import org.jtell.config.ConfigurationSource;
import org.jtell.config.annotation.AnnotationConfigurationSource;
import org.jtell.config.annotation.AnnotationEventMetadataSource;

import java.util.List;
import java.util.ArrayList;

/**
 * <p>
 * <code>JTellContextBuilderImpl</code> is the concrete implementation of the {@link JTellContextBuilder} interface.
 * </p>
 * <p>
 * <strong>Thread Safety</strong><br/>
 * Instances of this class are not thread safe. All access must be confined to a single thread.
 * </p>
 */
public class JTellContextBuilderImpl implements JTellContextBuilder
{
    private static Log LOG = LogFactory.getLog(JTellContextBuilderImpl.class);

    /**
     * <p>
     * The event sink sources.
     * </p>
     */
    private final List<EventSinkSource> m_eventSinkSources = new ArrayList<EventSinkSource>();

    /**
     * <p>
     * The event metadata source.
     * </p>
     */
    private List<EventMetadataSource> m_eventMetadataSources = new ArrayList<EventMetadataSource>();

    /**
     * <p>
     * The configuration source.
     * </p>
     */
    private ConfigurationSource m_configurationSource;

    /**
     * <p>
     * The event sink registry. 
     * </p>
     */
    private EventSinkRegistry m_eventSinkRegistry;

    /**
     * <p>
     * Construct a {@link JTellContextBuilderImpl} instance.
     * </p>
     */
    public JTellContextBuilderImpl()
    {
        super();
    }

    /* inherit javadoc */
    public JTellContextBuilder addEventMetadataSource(final EventMetadataSource eventMetadataSource)
    {
        Guard.notNull("eventMetadataSource", eventMetadataSource);
        m_eventMetadataSources.add(eventMetadataSource);
        return this;
    }

    /* inherit javadoc */
    public JTellContextBuilder addEventSinkSource(final EventSinkSource eventSinkSource)
    {
        Guard.notNull("eventSinkSource", eventSinkSource);
        m_eventSinkSources.add(eventSinkSource);
        return this;
    }

    /* inherit javadoc */
    public JTellContext createContext()
    {
        // Apply defaults where necessary.
        validateAndApplyDefaults();

        // Load the configuration.
        final Configuration configuration = m_configurationSource.getConfiguration();
        if (LOG.isDebugEnabled())
        {
            LOG.debug(String.format("Creating context using configuration [%s].", configuration));
        }

        // Create the context.
        final JTellContext result = new JTellContextImpl(configuration, m_eventMetadataSources, m_eventSinkRegistry,
                m_eventSinkSources);
        if (LOG.isInfoEnabled())
        {
            LOG.info(String.format("Returning new context [%s].", result));
        }
        return result;
    }

    /* inherit javadoc */
    public JTellContextBuilder setConfigurationSource(ConfigurationSource configurationSource)
    {
        Guard.notNull("configurationSource", configurationSource);
        Guard.propertyNull("configurationSource", m_configurationSource);
        m_configurationSource = configurationSource;
        return this;
    }

    /* inherit javadoc */
    public JTellContextBuilder setEventSinkRegistry(final EventSinkRegistry eventSinkRegistry)
    {
        Guard.notNull("eventSinkRegistry", eventSinkRegistry);
        Guard.propertyNull("eventSinkRegistry", m_eventSinkRegistry);
        m_eventSinkRegistry = eventSinkRegistry;
        return this;
    }

    /**
     * <p>
     * Validate the stored context configuration, applying any necessary defaults. 
     * </p>
     *
     * @throws IllegalStateException if the configuration is in an invalid state.
     */
    private void validateAndApplyDefaults() throws IllegalStateException
    {
        if (null == m_configurationSource)
        {
            m_configurationSource = new AnnotationConfigurationSource();
            if (LOG.isInfoEnabled())
            {
                LOG.info(String.format("Using default configuration source [%s].", m_configurationSource));
            }
        }
        if (m_eventMetadataSources.isEmpty())
        {
            if (m_configurationSource instanceof AnnotationConfigurationSource)
            {
                m_eventMetadataSources.add(new AnnotationEventMetadataSource());
            }
            m_eventMetadataSources.add(new DefaultEventMetadataSource());
            if (LOG.isInfoEnabled())
            {
                LOG.info(String.format("Using default event metadata source(s) [%s].", m_eventMetadataSources));
            }
        }
        if (null == m_eventSinkRegistry)
        {
            m_eventSinkRegistry = new ConcurrentEventSinkRegistry();
            if (LOG.isInfoEnabled())
            {
                LOG.info(String.format("Using default event sink registry [%s].", m_eventSinkRegistry));
            }
        }
        if (m_eventSinkSources.isEmpty())
        {
            m_eventSinkSources.add(new DefaultEventSinkSource());
            if (LOG.isInfoEnabled())
            {
                LOG.info(String.format("Added default event sink source(s) [%s].", m_eventSinkSources));
            }
        }
    }
}
